/*
Filename:    sctp_one_to_many_connection.cpp
Author:      Erik Öjebo
Date:        2009-01-10
Description: Represents an SCTP one-to-many connection. Can be both a listening
             socket and an ordinary socket.
*/

// See header file for documentation.

#include <stdexcept>
#include <iostream>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include "sctp_one_to_many_connection.h"

SctpOneToManyConnection::SctpOneToManyConnection()
{
    // Create and initialize a SCTP one-to-many socket:
    createSocket();

    // Call base class initialization to initialize state:
    initialize();
}

SctpOneToManyConnection SctpOneToManyConnection::
    createListeningConnection(const Address& address)
{
    SctpOneToManyConnection connection;

    // Bind the socket to the given address:
    connection.bind(address);

    // Make the socket a listening socket:
    connection.startListen();

    return connection;
}

SctpOneToManyConnection SctpOneToManyConnection::
    createListeningConnection(const std::vector<Address>& addresses)
{
    SctpOneToManyConnection connection;

    // Bind the socket to the specified addresses:
    connection.bind(addresses);

    // Make the socket a listening socket:
    connection.startListen();

    return connection;
}

SctpOneToManyConnection SctpOneToManyConnection::
    createListeningConnection(int portNumber)
{
    SctpOneToManyConnection connection;

    // Bind the socket to all available addresses:
    connection.bindToAll(portNumber);

    // Make the socket a listening socket:
    connection.startListen();

    return connection;
}

void SctpOneToManyConnection::createSocket()
{
    struct sctp_event_subscribe events;

    // Create a new SCTP one-to-many socket:
    socketFd = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);

    // If an error occurred, throw an exception:
    if (socketFd < 0)
    {
        throw std::runtime_error(
                "SctpOneToManyConnection: could not create socket");
    }

    // Set socket options
    memset(&events, '\0', sizeof(events));
    events.sctp_data_io_event = 1;
    events.sctp_association_event = 1;

    if ( setsockopt(socketFd, IPPROTO_SCTP, SCTP_EVENTS,
            &events, sizeof(events)) < 0)
    {
        throw std::runtime_error(
                "SctpOneToManyConnection: could not set socket options");
    }
}

ssize_t SctpOneToManyConnection::sendToAddress(
        void const * const buffer, size_t size, const Address& address) const
{
    ssize_t sentBytes;
    struct sctp_sndrcvinfo sri;

    // Clear the sndrcvinfo structure
    memset(&sri, '\0', sizeof(sri));

    // Set stream number
    sri.sinfo_ssn = 0;

    // Send the message:
    sentBytes = sctp_sendmsg(socketFd, buffer, size,
        (sockaddr*)address.getSocketAddress(), address.getSocketAddressSize(),
        0, SCTP_ADDR_OVER, sri.sinfo_ssn, 0, 0);

    return sentBytes;
}

void SctpOneToManyConnection::
    sendToAll(void const * const buffer, size_t size) const
{
    struct sctp_sndrcvinfo sri;
    AssociationCollectionConstIterator assocIterator = associations.begin();
    ssize_t sentBytes;

    // For each association in the collection, send the message:
    for (; !assocIterator.isAtEnd(); assocIterator++)
    {
        // Initialize send/receive information:
        memset(&sri, '\0', sizeof(sri));
        sri.sinfo_assoc_id = assocIterator.data();
        sri.sinfo_stream = 0;

        // Send the message:
        sentBytes = sctp_send(socketFd, buffer, size, &sri, 0);
    }
}

void SctpOneToManyConnection::setAutoCloseTime(int seconds)
{
    int autocloseTime = seconds;

    // Set the autoclose time using socket options:
    // Note: The autoclose time will only be applied to new associations,
    // not to already existing ones.
    if ( setsockopt(socketFd, IPPROTO_SCTP, SCTP_AUTOCLOSE, &autocloseTime,
            sizeof(autocloseTime)) < 0)
    {
        throw std::runtime_error(
                "SctpOneToManyConnection: could not set autoclose time");
    }
}

SctpOneToOneConnection SctpOneToManyConnection::
    peelOff(sctp_assoc_t associationId)
{
    // Peel off the association into it's own one-to-one socket:
    int peeledFd = sctp_peeloff(socketFd, associationId);

    // Force the association to be removed from the collections since
    // no sctp_assoc_change event is triggered by the peel off:
    associations.remove(associationId);

    // Return a One-to-one connection for the peeled off socket:
    return SctpOneToOneConnection(peeledFd);
}

